/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Gimme Object Module:	Wrapper around Gimme Query
*
*	Requires:
*	Gimme.query.js
*
*/

(function()
{
	var help = Gimme.Helper;
	//,contains = h.contains
	//,indexOf = h.indexOf
	//,trim = h.trim
	//,normalize = h.normalize
	//,hasClass = h.hasClass
	//,attrExists = h.attrExists
	//,readAttr = h.readAttribute
	//,getObjectGUID = h.getObjectGUID
	//,convertToPixels = h.convertToPixels;

	// core Gimme extension functions
	Gimme.ext.element = function(n)
	{
		return this[n || 0];
	};

	Gimme.ext.parent = function(n)
	{
		var elem = this[n || 0];
		return Gimme(elem && elem.parentNode);
	};
	
	Gimme.ext.has = function(descendant, n)
	{
		var elem = this[n || 0];
		return Gimme(descendant, elem).length > 0;
	};

	Gimme.ext.add_class = function(_class)
	{
		var classes = _class.split(/\s+/);
		
		this.for_each(function(_elem)
		{
			Gimme(classes).for_each(function(_className)
			{
				if (!help.has_class(_elem, [ _className ]))
				{
					if (_elem.className === '')
					{
						_elem.className = _className;
					}
					else
					{
						_elem.className += ' ' + _className;
					}
				}
			});
		});
		
		return this;
	};

	Gimme.ext.remove_class = function(_class)
	{
		return this.swap_class(_class, '$1');
	};

	Gimme.ext.swap_class = function(_oldClass, _newClass)
	{
		// if _newClass is '$1' it means we're actually just removing _oldClass (and not replacing it with anything)
		// however, if _newClass is anything other than $1, we need to pad it with spaces 
		if (_newClass !== '$1')
		{
			_newClass = ' ' + _newClass + ' ';
		}
		
		var classes = _oldClass.split(/\s+/);
		
		this.for_each(function(_elem)
		{
			var tmpClass = _elem.className;
			
			Gimme(classes).for_each(function(_className)
			{
				var pattern = new RegExp('(^| )' + _className + '( |$)');
				tmpClass = tmpClass.replace(pattern, _newClass);
			});
			
			// TODO: was this really needed?  Can we get away with just trimming?
			// _elem.className = normalize(tmpClass);
			_elem.className = tmpClass.replace(/^\s+|\s+$/g, '');			
		});
		
		return this;
	};

	Gimme.ext.has_class = function(_class, n)
	{
		var elem = this[n || 0];
		return elem && help.has_class(elem, [ _class ]) || false;
	};
	
	Gimme.ext.get_native_prop = function(property, n)
	{
		var elem = this[n || 0];
		return elem ? elem[property] : '';
	};

	Gimme.ext.get_ancestor = function(_distance, n)
	{
		var elem = this[n || 0];
		var i = _distance;
		while (i-- > 0)
		{
			if (elem)
			{
				elem = elem.parentNode;
			}
			else
			{
				break;
			}			
		}
		
		return Gimme(elem);
	};

	Gimme.ext.get_sibling = function(_distance, n)
	{
		var elem = this[n || 0];
		var sib = elem;
		
		if (_distance !== 0)
		{
			var sibling = _distance > 0 ? 'nextSibling' : 'previousSibling';
			var count = Math.abs(_distance);

			var i = 0;
			while (i < count)
			{
				sib = sib[sibling];
				if (!sib)
				{
					break;
				}
				if (sib.nodeType === 1)
				{
					i++;
				}
			}
		}
		
		return Gimme(sib);
	};

	Gimme.ext.select = function(selector)
	{
		return Gimme(selector, this[0]);
	};

	Gimme.ext.set_html = function(_html)
	{
		this.for_each(function(_elem)
		{
			_elem.innerHTML = _html;
		});

		return this;
	};

	Gimme.ext.get_html = function(n)
	{
		var elem = this[n || 0];
		return elem && elem.innerHTML || '';
	};

	Gimme.ext.set_val = function(_val)
	{
		this.for_each(function(_elem)
		{
			if (typeof _elem.value !== 'undefined')
			{
				_elem.value = _val;
			}
		});
			
		return this;
	};

	Gimme.ext.get_val = function(n)
	{
		var elem = this[n || 0];
		return elem && elem.value || '';
	};

	Gimme.ext.get_attr = function(_attr, n)
	{
		var elem = this[n || 0];
		return elem && help.get_attr(elem, _attr) || '';
	};

	Gimme.ext.set_attr = function(_attr, _val)
	{
		this.for_each(function(_elem)
		{
			_elem.setAttribute(_attr, _val);
		});
		
		return this;
	};

	Gimme.ext.filter = function(filterFn)
	{
		var gim = Gimme();
		var i = this.length;
		while (i--)
		{
			filterFn(this[i]) && gim.unshift(this[i]);
		}
		return gim;
	};

	Gimme.ext.iterate = function(callback)
	{
		var i, len = this.length;
		for (i = 0; i < len; i++)
		{
			var gim = Gimme(this[i]);
			callback.call(gim, i);
		}
		
		_callback = null;
		
		return this;
	};

	Gimme.ext.get_style = function(style, n)
	{
		var elem = this[n || 0];
		if (elem)
		{
			return style === 'opacity' ? getOpacity(elem) : getRenderedStyle(elem, style);
		}
		return '';
	};

	Gimme.ext.set_style = function(dict)
	{
		this.for_each(function(elem)
		{
			var key;
			for (key in dict)
			{
				switch (key)
				{
					case 'opacity':
						setOpacity(elem, dict[key]);
						break;
					default:
						elem.style[key] = dict[key];
						break;
				}
			}
		});
		return this;
	};
	
	Gimme.ext.add_event = function()
	{
		return document.addEventListener ?
			w3c :								// use w3c style event handling
			document.attachEvent ?
			ie :								// use ie style event handling
			function()
			{									// no modern event support :-(
			};
			
		function w3c(eventName, handler, useCapture, pseudoCall)
		{
			if (!pseudoCall && Gimme.Events && Gimme.Events.Pseudo && Gimme.Events.Pseudo[eventName])
			{
				Gimme.Events.Pseudo[eventName].add(this, handler, useCapture);
			}
			else
			{
				this.for_each(function(elem)			
				{
					elem.addEventListener(eventName, handler, useCapture);
				});
			}
			
			return this;
		}
		
		function ie(eventName, handler, useCapture, pseudoCall)
		{
			function normalizeEvent(elem, e)
			{
				// set the .eventPhase
				// 1 = capturing (the default start value, but this can change during the event's lifecycle)
				// 2 = at target (theoretically, but I haven't found a good way of setting this)
				// 3 = bubbling
				e.eventPhase = e.eventPhase || 1;
				
				// map .target to .srcElement
				e.target = e.srcElement;
				
				// map .currentTarget to _elem (so the element dispatching the events can be accessed)
				e.currentTarget = elem;
				
				switch (e.type)
				{
					case 'keyup':
					case 'keydown':
					case 'keypress':
						// IE no likely when you try to monkey with key* properties of a file input
						if (elem.type !== 'file')
						{
							// try to add some sanity to the which/keyCode mess (see http://unixpapa.com/js/key.html)
							e.keyCode === 186 ? e.keyCode = 59 :				// map IE's keyCode for ";:" to Mozilla's
							e.keyCode === 187 ? e.keyCode = 61 :				// map IE's keyCode for "=+" to Mozilla's
							e.keyCode === 189 ? e.keyCode = 109 : null;			// map IE's keyCode for "-_" to Mozilla's
						}
						e.which = e.keyCode;									// e.which is either e.keyCode or e.charCode, whichever is "set" (applicable only to keydown)
						e.charCode = e.keyCode;									// in IE: keypress/keyCode is charCode, keydown/charCode is not useful and should not be used
						break;
						
					case 'mousedown':
					case 'mouseup':
					case 'click':
					case 'dblclick':
					case 'contextmenu':
						// nifty IE e.button to FF e.which mapping
						e.which =  [ 1, 1, 3, e.button, 2 ][e.button];		// 3 should never happen in IE (it uses bit flags), but stuff in e.button just in case
						break;
						
					case 'mouseover':
						// map .relatedTarget to either .toElement
						e.relatedTarget = e.fromElement;
						break;
						
					case 'mouseout':
						// or .fromElement
						e.relatedTarget = e.toElement;
						break;
				}
				
				// add in support for .preventDefault() and .stopPropagation()
				e.preventDefault = function()
				{
					e.returnValue = false;
				};
				e.stopPropagation = function()
				{
					e.cancelBubble = true;
				};
				
				return e;
			};
		
			function storeCaptureData(eventName, elemGuid, handler)
			{
				var capturingEvent = capturingEventHash[eventName];
				if (!capturingEvent)
				{
					capturingEvent = capturingEventHash[eventName] = {};
				}

				var handlers = capturingEvent[elemGuid];
				if (!handlers)
				{
					handlers = capturingEvent[elemGuid] = [];
				}

				handlers.push(handler);			
			}
			
			function handleCapturingPhase(elem, e)
			{
				// determine the stack of ancestors that need to be checked for event capturing
				var stack = [];
				var parent = elem;
				while (parent && parent !== document)
				{
					stack.push(parent);
					parent = parent.parentNode;
				}
				
				// execute all handler for any element that is listening for "captured" events,
				// but only if propagation /hasn't/ been stopped (e.cancelBubble = true)
				var current;
				while (current = stack.pop())
				{
					var currentGuid = help.get_guid(current);
					if (capturingEventHash[eventName][currentGuid])
					{
						var i, len = capturingEventHash[eventName][currentGuid].length;
						for (i = 0; i < len; i++)
						{
							if (!e.cancelBubble)
							{
								var eventHash = capturingEventHash[eventName];
								eventHash[currentGuid] && eventHash[currentGuid][i] && eventHash[currentGuid][i].call(current, e);
							}
						}
					}
				}
			}
			
			if (!pseudoCall && Gimme.Events && Gimme.Events.Pseudo && Gimme.Events.Pseudo[eventName])
			{
				Gimme.Events.Pseudo[eventName].add(this, handler, useCapture);
			}
			else
			{
				this.for_each(function(elem)
				{				
					// create a key to identify this element/event/function combination
					var elemGuid = help.get_guid(elem);
					var handlerGuid = help.get_guid(handler) + (useCapture || false).toString();
					var key = '{' + elemGuid + ' / ' + eventName + ' / ' + handlerGuid + '}';			
					
					if (useCapture)
					{
						storeCaptureData(eventName, elemGuid, handler)
					}
					
					var proxy = handlerHash[key];
					if (!proxy)
					{
						proxy = function(e)
						{
							e = normalizeEvent(elem, e);
							
							if (capturingEventHash[eventName] && e.eventPhase === 1)
							{
								handleCapturingPhase(elem, e);
							}
							
							e.eventPhase = 3;
							
							!useCapture && !e.cancelBubble && handler.call(elem, e);
							
							// cleanup
							e.eventPhase =
							e.target =
							e.currentTarget =
							e.relatedTarget
							e.which =
							e.preventDefault =
							e.stopPropagation =
							e = null;						
						};
						
						handlerHash[key] = proxy;
						elem.attachEvent('on' + eventName, proxy);
					}
					
					key = null;
					proxy = null;
				});
			}
			
			return this;
		}
	}();
	
	Gimme.ext.remove_event = function()
	{
		return document.addEventListener ?
			w3c :								// use w3c style event handling
			document.attachEvent ?
			ie :								// use ie style event handling
			function()
			{									// no modern event support :-(
			};
		
		function w3c(eventName, handler, useCapture, pseudoCall)
		{
			if (!pseudoCall && Gimme.Events && Gimme.Events.Pseudo && Gimme.Events.Pseudo[eventName])
			{
				Gimme.Events.Pseudo[eventName].remove(this, handler, useCapture);
			}
			else
			{
				this.for_each(function(elem)			
				{
					elem.removeEventListener(eventName, handler, useCapture);
				});
			}		
			
			return this;
		}
		
		function ie(eventName, handler, useCapture, pseudoCall)
		{
			function removeCaptureData(eventName, elemGuid, handler)
			{
				var capturingEvent = capturingEventHash[eventName];
				if (capturingEvent)
				{
					var handlers = capturingEvent[elemGuid];
					if (handlers)
					{
						var i = Gimme(handlers).index_of(handler);
						if (i !== -1)
						{
							Gimme(handlers).splice(i, 1);
						}
						if (handlers.length === 0)
						{
							delete capturingEvent[elemGuid];
						}
					}
				}
			}

			if (!pseudoCall && Gimme.Events && Gimme.Events.Pseudo && Gimme.Events.Pseudo[eventName])
			{
				Gimme.Events.Pseudo[eventName].remove(this, handler, useCapture);
			}
			else
			{			
				this.for_each(function(elem)
				{
					// grab the event handler function from the hash based on its key
					// create a key to identify this element/event/function combination
					var elemGuid = help.get_guid(elem);
					var handlerGuid = help.get_guid(handler) + (useCapture || false).toString();
					var key = '{' + elemGuid + ' / ' + eventName + ' / ' + handlerGuid + '}';
					
					if (useCapture)
					{
						removeCaptureData(eventName, elemGuid, handler)
					}
					
					var proxy = handlerHash[key]
					if (proxy)
					{
						// unhook the event and delete the element from the event hash
						elem.detachEvent('on' + eventName, proxy);
						delete handlerHash[key];
					}

					// prevent IE memory leaks
					key = null;
					proxy = null;
				});
			}
				
			return this;			
		}
	}();
	
	Gimme.ext.for_each = function()
	{
		return typeof Array.prototype.forEach !== 'undefined' ? native_forEach : custom_forEach;
		
		function native_forEach(fn, thisObject)
		{
			this.entities().forEach(fn, thisObject);
			return this;
		}
		
		function custom_forEach(fn, thisObject)
		{
			var things = this.entities();
			var i, len = things.length;
			for (i = 0; i < len; i++)
			{
				fn.call(thisObject, things[i], i, things);
			}
			
			return this;
		}
	}();

	Gimme.ext.map = function()
	{
		return typeof Array.prototype.map !== 'undefined' ? native_map : custom_map;
		
		function native_map(fn, thisObject)
		{
			return Gimme(this.entities().map(fn, thisObject));
		}
		
		function custom_map(fn, thisObject)
		{
			var gim = Gimme();
			var i = this.length;
			while (i--)
			{
				gim.unshift(fn.call(thisObject, this[i]));
			}
			return gim;
		}
	}();
	
	Gimme.ext.unique = function()
	{
		return Gimme(help.unique(this.entities()));
	}

	Gimme.ext.contains = function(item)
	{
		return help.contains(this.entities(), item);
	};

	Gimme.ext.index_of = function(item)
	{
		return help.index_of(this.entities(), item);
	};
	
	Gimme.ext.get_absolute_pos = function(n)
	{
		var elem = this[n || 0];
		return elem && getAbsolutePos(elem) || { x: 0, y: 0 };
	};
	
	Gimme.ext.get_relative_pos = function(n)
	{
		var elem = this[n || 0];
		return elem && getRelativePos(elem) || { x: 0, y: 0 };
	};	
	
	Gimme.ext.get_style_pos = function(n)
	{
		var elem = this[n || 0];
		return { x: parseFloat(getRenderedStyle(elem, 'left')), y: parseFloat(getRenderedStyle(elem, 'top')) };
	};
	
	Gimme.ext.get_scroll_pos = function(n)
	{
		var elem = this[n || 0];		
		var pos = { x: 0, y: 0 };		
		if (elem === window)
		{
			// window.pageYOffset is used by Firefox and Mozilla browsers, Safari, Opera, and Konqueror
			if (typeof window.pageYOffset !== 'undefined')
			{
				pos.x = window.pageXOffset;	
				pos.y = window.pageYOffset;
			}
			// document.documentElement.scrollTop is used by IE6 in standards-compliant mode
			else if (!Gimme.Browser.is_in_quirks_mode && typeof document.documentElement.scrollTop !== 'undefined')
			{
				pos.x = document.documentElement.scrollLeft;
				pos.y = document.documentElement.scrollTop;
			}
			// document.body.scrollTop is used by IE6 in "Quirks" mode
			else if (typeof document.body.scrollTop !== 'undefined')
			{
				pos.x = document.body.scrollLeft;
				pos.y = document.body.scrollTop;
			}
		}
		else
		{
			pos.x = elem.scrollLeft;
			pos.y = elem.scrollTop;
		}
		return pos;
	}
	////
	
	
	// private members
	var handlerHash = {};
	var capturingEventHash = {};
	////
	
	
	// private methods	
	function getRelativePos(elem)
	{
		var useScroll = elem.scrollTop
						&& elem !== document.documentElement
						&& elem !== document.body
						&& elem.tagName !== 'TEXTAREA'
						&& elem.tagName !== 'INPUT'
						? -1 : 0;
			
		var pos =
		{
			x: elem.scrollLeft * useScroll,
			y: elem.scrollTop * useScroll
		};
		
		if (Gimme.Browser.is_ie && getRenderedStyle(elem, 'position') === 'static')
		{
			var tmp = elem.style.position;
			elem.style.position = 'relative';
			pos.x += elem.offsetLeft;
			pos.y += elem.offsetTop;
			elem.style.position = tmp;
		}
		else
		{
			pos.x += elem.offsetLeft;
			pos.y += elem.offsetTop;
		}
		
		return pos;
	};
	
	function getAbsolutePos(elem)
	{
		// the method will return incorrect value when elem is of type SVGPathElement and browser == Chrome
		// http://code.google.com/p/chromium/issues/detail?id=47998
		var pos = { x: 0, y: 0 };
		
		if (elem.getBoundingClientRect)
		{
			var rect = elem.getBoundingClientRect();
			var offsets = getBoundingClientRectOffsets();
			var scrollPos = Gimme(window).get_scroll_pos();
			pos.x = rect.left - offsets.x + scrollPos.x
			pos.y = rect.top - offsets.y + scrollPos.y;
		}
		else
		{		
			var tmp;
			while (elem !== null)
			{		
				tmp = getRelativePos(elem);
				pos.x += tmp.x;
				pos.y += tmp.y;
				
				elem = elem.offsetParent;			
				if (elem && !offsetIncludesBorders())
				{
					pos.x += parseFloat(getRenderedStyle(elem, 'borderLeftWidth')) || 0;
					pos.y += parseFloat(getRenderedStyle(elem, 'borderTopWidth')) || 0;
				}			
			}
		}
		return pos;	
	}
	
	function getRenderedStyle(elem, style)
	{	
		var val = '';
		
		// can't trust .currentStyle.right or .currentStyle.bottom so special case to compute those manually
		if (elem.currentStyle && (style === 'right' || style === 'bottom'))
		{
			val = normalizeStyle(style, 'auto', 'auto', elem) + 'px';
		}
		else
		{
			var cs = (document.defaultView && document.defaultView.getComputedStyle && document.defaultView.getComputedStyle(elem, null)) || elem.currentStyle;
			if (cs)
			{			
				var val = cs[style] || '';
				var isPixels = val.match(/px$/);
				if (!isPixels)
				{
					var units = val.match(/(em|ex|%|in|cm|mm|pt|pc|small|medium|large|thin|thick|auto)$/);
					if (units)
					{
						val = normalizeStyle(style, val, units[0], elem) + 'px';
					}
				}
			}
		}
		return val;
	}
	
	function normalizeStyle(style, val, units, context)
	{
		if (!val)
		{
			return 0;
		}		
		else if (units === 'px')
		{
			return parseFloat(val);
		}
		else if (val === 'auto')
		{
			// handle styles that can return a keyword value of "auto"
			switch (style)
			{
				case 'top':
				case 'left':
					return 0;
				
				// Notes about element position:
				//
				// - position: static -
				// Elements that are statically positioned are not supported here. Nothing
				// useful comes from setting the bottom/right (or top/left for that matter)
				// of a statically positioned element.
				//
				// - position: relative -
				// Elements that are relatively positioned always interpret their
				// top/right/bottom/left styles from the position they would normally
				// occupy if no position where set. The net effect of this is an
				// "opposite" relationshiop between the left/right properties and
				// the top/bottom properties of a relatively positioned element.
				// For instance, setting a relatively positioned element to either
				// top: 100px; or bottom: -100px yields the same result!
				// This comment should make clear the code below that returns the
				// negative of left when right is requested or the negative of
				// top when bottom is requested.
				//
				// - position: absolute -
				// This is the easiest case in browsers that support getComputedStyle.
				// In such cases, we don't even get to this function.  In any browser
				// that doesn't though (looking at you, IE), we have to compute right
				// (or bottom) based on a combination of the given element's left
				// position and its parent's width (or its top position and its parent's
				// height). Not too hard, but a little messy.

				// Notes about DRY (don't repeat yourself).
				//
				// The logic for right and bottom are nearly identical:
				//
				// elem_right_pos = parent_width - elem_left_pos - elem_width
				// elem_bottom_pos = parent_height - elem_top_pos - elem_height
				//
				// We don't want to duplicate the logic when the only difference
				// is a few property names.  A "propertyNames" dictionary (pn)
				// acts as an easy way to pluck out the property names we need
				// without duplicating any code					
				case 'bottom':
				case 'right':
					// PropertyNames dictionary (avoids the needs for duplicate code)
					var pn =
					{
						bottom:	[ 'top', 'offsetTop', 'height', 'offsetHeight' ],
						right: [ 'left', 'offsetLeft', 'width', 'offsetWidth' ]
					};
					
					var gim = Gimme(context);
					if (gim.get_style('position') === 'relative')
					{
						// this will "magically" be whichever one we need it to be
						var left_or_top = pn[style][0];
						
						// yes, this is on purpose (see comments above)
						return -parseInt(gim.get_style(left_or_top));
					}
					else
					{
						// need an ancestor that isn't position: static
						var parent = context.parentNode;
						while (parent !== document && Gimme(parent).get_style('position') === 'static')
						{
							parent = parent.parentNode;
						}
						
						var width_or_height = pn[style][2];
						var parentSize;
						if (parent === document)
						{
							// in which case, use the viewport instead
							parentSize = Gimme.Screen.getViewportSize()[width_or_height];
						}
						else
						{
							// otherwise, whatever non-staic ancestor that was found will do
							parentSize = parseInt(Gimme(parent).get_style(width_or_height));
						}
						
						var offsetLeft_or_offsetTop = pn[style][1];
						var offsetWidth_or_offsetHeight = pn[style][3];
						return parentSize - context[offsetLeft_or_offsetTop] - context[offsetWidth_or_offsetHeight];
					}
					
				case 'height':
					return	context.clientHeight ||
							(context.offsetHeight && context.offsetHeight
							- parseFloat(getRenderedStyle(context, 'borderTopWidth'))
							- parseFloat(getRenderedStyle(context, 'borderBottomWidth'))) || 0;
					
				case 'width':
					return	context.clientWidth ||
							(context.offsetWidth && context.offsetWidth
							- parseFloat(getRenderedStyle(context, 'borderLeftWidth'))
							- parseFloat(getRenderedStyle(context, 'borderRightWidth'))) || 0;
					
				// TODO:
				// case 'marginLeft':
				// case 'marginTop':
				// case 'marginRight':
				// case 'marginBottom':
					// non trivial when the returned value is "auto"
					
				default:
					return 0;
			}
		}
		else
		{
			// Special case for dealing with border widths:
			// IE will return a width (possibly as a keyword rather than a numeric value)
			// even if the border style of the element in question is set to none
			switch (style)
			{
				case 'borderLeftWidth':
				case 'borderRightWidth':
				case 'borderTopWidth':
				case 'borderBottomWidth':
					style = style.replace('Width', 'Style');
					var borderStyle = getRenderedStyle(context, style);
					return borderStyle === 'none' ? 0 : parseFloat(borderStyle);
			}
			
			
			// Let the real grunt-work begin!
			context = context || document.body;
			
			// 1. Create an invisible div that we'll use to measure the style represented by the style variable, val
			// 2. Make sure its position is absolute so it doesn't shift other elements on the page
			// 3. Make sure line-height is 0 or IE will add mysterious extra space to our measurements
			var tmp = document.createElement('div');
			tmp.style.visbility = 'hidden';
			tmp.style.position = 'absolute';				
			tmp.style.lineHeight = '0';
			
			// If  we're tyring to convert a % to pixels, we have to do it in the context of a parent element
			if (units === '%')
			{
				context = context.parentNode;
				
				if (style === 'height' || style === 'top')
				{
					tmp.style.height = val;
				}
				else if (style === 'width' || style === 'left')
				{
					tmp.style.width = val;
				}
			}
			else
			{
				tmp.style.borderStyle = 'solid';
				tmp.style.borderBottomWidth = '0';					
				tmp.style.borderTopWidth = val;
			}
			
			try
			{
				context.appendChild(tmp);
			}
			catch (ex)
			{
				context = context.parentNode;
				context.appendChild(tmp);
			}
			
			
			context.offsetWidth;							// IE Hack:
			context.offsetHeight;							// just referencing these properties of the context elem, seems to force measurement of the child elements
			
			var px = tmp.offsetHeight || tmp.offsetWidth;
			context.removeChild(tmp);
			
			return px;
		}
	}
	
	var setOpacity = function()
	{
		function w3c(elem, opacity)
		{
			elem.style.opacity = opacity;
		}
		
		function ie(elem, opacity)
		{
			opacity *= 100;
			
			var filter;
			try				
			{
				filter = elem.filters.item('DXImageTransform.Microsoft.Alpha');
				if (opacity < 100)
				{
					filter.Opacity = opacity;
					if (!filter.enabled)
					{
						filter.enabled = true;
					}
				}
				else
				{
					filter.enabled = false;
				}
			}
			catch (ex)
			{
				if (opacity < 100)
				{
					elem.style.filter = (elem.currentStyle || elem.runtimeStyle).filter + ' progid:DXImageTransform.Microsoft.Alpha(opacity=' + opacity + ')';
				}
			}		
		}
		
		var d = document.createElement('div');
		return typeof d.style.opacity !== 'undefined' && w3c
			|| typeof d.style.filter !== 'undefined' && ie
			|| function() {};
	}();

	var getOpacity = function()
	{
		function w3c(elem)
		{
			return parseFloat(elem.style.opacity) || getRenderedStyle(elem, 'opacity');
		}
		function ie(elem)
		{
			var filter = elem.filters['DXIMageTransform.Microsoft.Alpha'];
			if (filter)
			{
				return filter.Opacity / 100;
			}
			else
			{
				return 1;
			}
		}
		
		var d = document.createElement('div');
		return typeof d.style.opacity !== 'undefined' && w3c
			|| typeof d.style.filter !== 'undefined' && ie
			|| function() {};
	}();
	
	function offsetIncludesBorders()
	{
		if (typeof arguments.callee.constantValue === 'undefined')
		{
			var a = document.createElement('div');
			a.setAttribute('style', 'position:absolute;visibility:hidden;top:0;left:0;border:1px solid #000;');
			var b = document.createElement('div');			
			a.appendChild(b);
			document.body.appendChild(a);			
			arguments.callee.constantValue = b.offsetTop === 1;
			document.body.removeChild(a);
			a = b = null;
		}
		return arguments.callee.constantValue;
	}
	
	function getBoundingClientRectOffsets()
	{
		if (typeof arguments.callee.constantValue === 'undefined')
		{
			var box = document.body;
			var pos = box.style.position;
			var margin = box.style.margin;
			var border = box.style.border;
			box.style.position = 'static';
			box.style.margin = '0';
			box.style.border = '1px solid transparent';
			var rect = box.getBoundingClientRect();
			var scrollPos = Gimme(window).get_scroll_pos();
			arguments.callee.constantValue = { x: rect.left + scrollPos.x, y: rect.top + scrollPos.y };
			box.style.position = pos;
			box.style.margin = margin;
			box.style.border = border;
		}
		return arguments.callee.constantValue;
	}
	////
	
	
	// map g to Gimme if it hasn't already been taken
	if (typeof g === 'undefined')
	{
		window.g = Gimme;
	}
	////
	
})();

